#!/usr/bin/env jython
"""
 An example of a GUI app in Jython,
 for the April 2010 Programming Workshop at Marlboro College
 at http://cs.marlboro.edu/courses/spring2010/programming/home .

 The public repository for this project
 is http://code.google.com/p/marlboro-jython-gui/ .
 
 This implements the classic "15 puzzle";
 see for example http://en.wikipedia.org/wiki/Fifteen_puzzle .
 
 TODO:
  * put pieces of an image into each button.
  * put "change size" into settings pane
  * indication of which buttons are clickable?
    - could disable ones that aren't adjacent to empty ...
      but they'll look different in the default interface.
  * animate sliding of square - move with mouse while its down.
    - absolute positions for squares instead of Grid ?
      Can we use Grid layout and still adjust positions??  Hmmm.
    - fix window size ?
  
 Copyright (c) Jim Mahoney (mahoney@marlboro.edu), April 2010.
 Released under the MIT License (http://creativecommons.org/licenses/MIT/).
"""

from java.awt import Font, BorderLayout, GridLayout, Color, Panel
from javax.swing import JFrame, JPanel, JButton, JTabbedPane, JLabel


def zeroAndOne(a, b):
    """ True if the first is zero and the abs of the second is one. """
    return a == 0 and abs(b) == 1

def adjacent((x1, y1), (x2, y2)):
    """ True if the two points are 1 apart horizontally or vertically"""
    dx, dy = (x1-x2), (y1-y2)
    return zeroAndOne(dx, dy) or zeroAndOne(dy, dx)

class App(JFrame):
    def __init__(self):
        JFrame.__init__(self, 'Sliding Block Puzzle',
                        size = (400, 400),
                        resizable = False,
                        defaultCloseOperation = JFrame.EXIT_ON_CLOSE)
        self.tabby = JTabbedPane()
        self.add(self.tabby)
        for name, pane in zip(['Puzzle', 'Settings', 'About'],
                              [Puzzle(), Settings(), About()]):
            self.tabby.add(pane, name)
        self.visible = True

class About(JPanel):
    def __init__(self):
        self.add(JLabel("It's just this GUI application, see?" ))

class Settings(JPanel):
    def __init__(self):
        self.add(JLabel(' ... coming ... '))

class Puzzle(JPanel):
    def __init__(self):
        self.N = 4   # N x N puzzle
        JPanel.__init__(self, layout=GridLayout(self.N, self.N))
        # self.buttons = []
        self.indeces = {'empty': self.N * self.N - 1}
        for index in range(self.N * self.N - 1):
            square = self.square(index)
            self.indeces[id(square)] = index
            # self.buttons.append(square)
            self.add(square)
        self.empty = JLabel('')
        self.add(self.empty)
    def square(self, index):
        """ Return one of the clickable puzzle squares. """
        # index runs left to right, top to bottom index
        square = JButton(str(index), 
                         font = Font('Arial', Font.BOLD, 48),
                         contentAreaFilled = False,
                         background = Color(0.2, 0.5, 0.8),
                         focusPainted = False)  # don't stay selected after click
        ## -------
        ## I haven't been able to change the button background color;
        ## googling provides a discussion of default user interfaces
        ## the how it isn't a bug to not be able to override them.  Sigh
        square.background = Color(0.2, 0.5, 0.8)
        square.setContentAreaFilled(False)
        ## One thread said that last line would help ... but I didn't see a change.
        ## ------
        self.indeces[id(square)] = index
        square.mouseClicked = lambda event: self.squareClick(square, event)
        return square
    def swap(self, square):
        emptyIndex, squareIndex = self.indeces['empty'], self.indeces[id(square)]
        self.add(self.empty, squareIndex)
        self.add(square, emptyIndex)
        self.indeces['empty'], self.indeces[id(square)] = squareIndex, emptyIndex
    def squareClick(self, square, event):
        if self.nearEmpty(square):
            self.swap(square)
            self.revalidate()
    def id2point(self, id):
        """ Convert an a square id to an (x,y) point"""
        index = self.indeces[id]
        y = int(index / self.N)
        x = index % self.N
        return x, y
    def nearEmpty(self, square):
        """ Return True if this square is next to the empty spot. """
        return adjacent(self.id2point('empty'), self.id2point(id(square)))                

if __name__ == '__main__':
    app = App()

